//-----------------------------------------------------------------------------
// File: App.cpp
// Copyright (c) 2005 ATI Technologies Inc. All rights reserved.
//-----------------------------------------------------------------------------


#include "Util.h"
#include "App.h"
#include <stdio.h>
#include "objLoader/objLoader.h"
#include "Util.h"
#include "Listener.h"
#include "XnTypes.h"
#include "NetworkDGram.h"

BaseApp *app = new App();


void App::initGUI(){
	// Initialize menu items
	menu = mainMenu->addMenu("FBO");
	render2D    = menu->addItem("Render to 2D",      MENU_CHECKABLE | MENU_CHECKED);
	renderCube  = menu->addItem("Render to Cubemap", MENU_CHECKABLE);
	renderDepth = menu->addItem("Render to Depth",   MENU_CHECKABLE);
	menu->setRadioButtonGroup(render2D, renderDepth, 1);

	GLApp::initGUI();
}

void App::setup(char *args[], int nArguments){
	width  = 800;
	height = 600;

	speed = 400;
	blurFactor=0.0f;
}

void App::resetCamera(){
	camPos = vec3(0, 300, -400);
	wx = 0.5f;
	wy = 0;
	pitchImpulse=0.0f;
	yawImpulse=0.0f;

	camQuat.fromAngleAxis(0.5f,vec3(1,0,0));
}

bool App::checkFrameBufferStatus(){
	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	switch (status){
		case GL_FRAMEBUFFER_COMPLETE_EXT:
			//INFOMSG("GL_FRAMEBUFFER_COMPLETE_EXT");
			return true;
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			ERRORMSG("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT");
			break;
		case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			ERRORMSG("GL_FRAMEBUFFER_UNSUPPORTED_EXT");
			break;
		default:
			ERRORMSG("Unknown FBO error");
			break;
	}
	return false;
}

int	mapTextureDim=1024;

extern vec2	lastClickUV;
extern vec2	clickUV;

bool	SSLoad(const char*	root,const char*	path);

std::map<std::string,TextureID>	gTextureIDCache;

TextureID getTextureByName(char*	name,TextureFilter	filter,uint flags=0){
	std::map<std::string,TextureID>::iterator fnd=gTextureIDCache.find(name);
	if(fnd!=gTextureIDCache.end())return fnd->second;
	Renderer *renderer=gApp->getRenderer();
	TextureID rtn = renderer->addTexture(name, filter);
	gTextureIDCache[name]=rtn;
	return rtn;
}

bool App::load(){
	gApp = this;

	if (!GLSL_Flag){
		ERRORMSG("No GLSL support");
		return false;
	}
	if (!GL_EXT_framebuffer_object_Flag){
		ERRORMSG("No FBO support");
		return false;
	}
	//wglSwapIntervalEXT(1);


	// Create color buffers
	glGenTextures(1, &color2D);
	glBindTexture(GL_TEXTURE_2D, color2D);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mapTextureDim, mapTextureDim, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

	// Create depth texture
	glGenTextures(1, &depth2D);
	glBindTexture(GL_TEXTURE_2D, depth2D);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, mapTextureDim, mapTextureDim, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);//GL_DEPTH_COMPONENT16

	glGenTextures(1, &colorCube);
	glBindTexture(GL_TEXTURE_CUBE_MAP, colorCube);
	// Initialize all faces and mipmap levels
	for (int size = 512, level = 0; size > 0; size >>= 1, level++){
		for (GLenum glTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X; glTarget <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; glTarget++){
			glTexImage2D(glTarget, level, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
		}
	}

	/*
		Hand off textures to resource manager. This is for convenience.	One important thing though, you should
		set the minify filter this early. This helps the driver know whether to allocate mipmaps or not.
	*/
	if ((rtColor2D   = ((GLRenderer *) renderer)->addTexture(color2D,   GL_TEXTURE_2D,       FILTER_BILINEAR,  0)) == TEXTURE_NONE) return false;
	if ((rtColorCube = ((GLRenderer *) renderer)->addTexture(colorCube, GL_TEXTURE_CUBE_MAP, FILTER_TRILINEAR, CLAMP)) == TEXTURE_NONE) return false;
	if ((rtDepth2D   = ((GLRenderer *) renderer)->addTexture(depth2D,   GL_TEXTURE_2D,       FILTER_LINEAR,    CLAMP)) == TEXTURE_NONE) return false;


	// Create depth buffer
	glGenRenderbuffersEXT(1, &depthBuffer);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, mapTextureDim, mapTextureDim);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);


	/*
		Create and setup FBOs
	*/
	glGenFramebuffersEXT(1, &fbo2D);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo2D);
		// Attach 2D color buffer
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, color2D, 0);
		// Attach depth buffer
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);

		if (!checkFrameBufferStatus()) return false;


#if 0
	glGenFramebuffersEXT(1, &fboCube);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboCube);
		// Attach cubemap face. This attachment will be changed in the rendering loop.
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, colorCube, 0);
		// Attach depth buffer
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);

		if (!checkFrameBufferStatus()) return false;
#endif	
		

	glGenFramebuffersEXT(1, &fboDepth);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboDepth);
		// Attach depth texture
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth2D, 0);

		// There are no color buffers attached
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);

		if (!checkFrameBufferStatus()) return false;


	// Restore to normal framebuffer operation
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);



	// Load shaders
	if ((lighting[0][0] = getShader("lighting.shd")) == SHADER_NONE) return false;
	if ((lighting[0][1] = getShader("lighting.shd", "#define USE_NORMALMAP\n")) == SHADER_NONE) return false;
	if ((lighting[1][0] = getShader("lighting.shd", "#define SHADOWS\n")) == SHADER_NONE) return false;
	if ((lighting[1][1] = getShader("lighting.shd", "#define SHADOWS\n#define USE_NORMALMAP\n")) == SHADER_NONE) return false;
	if ((depthOnly = getShader("lighting.shd", "#define DEPTH_ONLY\n")) == SHADER_NONE) return false;
	if ((sphereEnv = getShader("sphereEnv.shd")) == SHADER_NONE) return false;
	if ((viewDepth = getShader("viewdepth.shd")) == SHADER_NONE) return false;

	// Load textures
	//if ((chessBoard = renderer->addTexture(TEX_PATH "ChessBoard.dds", FILTER_TRILINEAR_ANISO)) == TEXTURE_NONE) return false;
	
	if ((lightFlareTexture = getTextureByName(TEX_PATH "particle0001.bmp", FILTER_BILINEAR)) == TEXTURE_NONE) return false;
	if ((sunTexture = getTextureByName(TEX_PATH "particle010001.bmp", FILTER_BILINEAR)) == TEXTURE_NONE) return false;
	if ((titleTexture = getTextureByName(TEX_PATH "titleSprite0001.png", FILTER_BILINEAR)) == TEXTURE_NONE) return false;


	sphere = createSphere(4);
	if (!sphere->makeDrawable(renderer)) return false;
	
	DWORD startT=GetTickCount();
	
	Listener::sendMessage("physicsBoot",this);
	Listener::sendMessage("engineBoot",this);
	Listener::sendMessage("physicsBuildSprites",this);
	
	Listener::sendMessage("networkStart");
	Listener::sendMessage("networkStartClient");



	DWORD dur=GetTickCount()-startT;
	logstr(format("Boot time:%f\n",(float)dur/1000.0f));
	//flushLog();

	return true;
}

bool App::unload(){

	Listener::sendMessage("engineShutdown");

	delete sphere;
	return true;
}

void App::drawLightQuad(const vec3 &lightPos, const vec3 &dx, const vec3 &dy,const float scale){
	glColor3f(1,1,1);
	glTexCoord2f(0, 0);
	glVertex3fv(lightPos + (scale * (-dx + dy)));
	glTexCoord2f(1, 0);
	glVertex3fv(lightPos + (scale * ( dx + dy)));
	glTexCoord2f(1, 1);
	glVertex3fv(lightPos + (scale * ( dx - dy)));
	glTexCoord2f(0, 1);
	glVertex3fv(lightPos + (scale * (-dx - dy)));
}

void App::drawLight(const vec3 &lightPos, const vec3 &dx, const vec3 &dy,const float scale){
	// Light drawn as billboarded quad
	renderer->setTexture(TMU0, lightFlareTexture);
	renderer->setBlending(GL_ONE, GL_ONE);
	renderer->setMask(COLOR);
	renderer->apply();
	glBegin(GL_QUADS);
	drawLightQuad(lightPos,dx,dy,scale);
	glEnd();
}

void App::drawSceneDepth(const vec3 &camPos, const float wx, const float wy){
	// Add a depth bias to avoid shadow mapping artifacts
	glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(1, 1);

	mat4 modelview = rotateXY4(-wx, -wy);
	modelview.translate(-camPos);

	glMatrixMode(GL_MODELVIEW);
	glLoadTransposeMatrixf(modelview);



	renderer->setShader(depthOnly);
	renderer->setCullFace(BACK);
	renderer->apply();

	//bunny->draw(renderer);
	//gEngine->render();

	glBegin(GL_QUADS);
		glVertex3f(-256, 0,  256);
		glVertex3f( 256, 0,  256);
		glVertex3f( 256, 0, -256);
		glVertex3f(-256, 0, -256);
	glEnd();

	glDisable(GL_POLYGON_OFFSET_FILL);
}

void	App::drawFSQuad(){
	// Draw the chess board
	renderer->setDepthFunc(DEPTH_NONE);
	renderer->setMask(COLOR);
	renderer->apply();
	glBegin(GL_QUADS);
		glTexCoord2f(0,  0);
		glVertex3f(-1,  1,0);
		glTexCoord2f(1,  0);
		glVertex3f( 1,  1,0);
		glTexCoord2f(1,  1);
		glVertex3f( 1, -1,0);
		glTexCoord2f(0,  1);
		glVertex3f(-1, -1,0);
	glEnd();
}

void	drawXYQuad(float scl)
{	
	glBegin(GL_QUADS);
		glTexCoord2f(0,  0);
		glVertex3f(-scl,  scl,0);
		glTexCoord2f(1,  0);
		glVertex3f( scl,  scl,0);
		glTexCoord2f(1,  1);
		glVertex3f( scl, -scl,0);
		glTexCoord2f(0,  1);
		glVertex3f(-scl, -scl,0);
	glEnd();
}

void	drawScreenQuad(float x,float y,float scl)
{
	glPushMatrix();
	glTranslatef(x,y,0.0);
	drawXYQuad(scl);
	glPopMatrix();
}


void App::drawScene(const vec3 &camPos, const float wx, const float wy, const Light *lights, const int nLights, const mat4 *shadowMvp){

	mat4 rm = rotateXY4(-wx+time, -wy+time);

	glMatrixMode(GL_MODELVIEW);
	glLoadTransposeMatrixf(camModelView);
	

	int shadows = (shadowMvp != NULL)? 1 : 0;

	vec3 dx = this->screenX = camModelView.getRightVec();
	vec3 dy = this->screenY = camModelView.getUpVec();
	
	Listener::sendMessage("engineRenderSolid");
	Listener::sendMessage("engineRender");


}

void	ONIupdate(void);
bool	ONIgetJoint(XnUserID player,XnSkeletonJoint eJoint1,float* out);

#define	GESTURE_FWD		0
#define	GESTURE_BACK	1
#define	GESTURE_LEFT	2
#define	GESTURE_RIGHT	3
#define	GESTURE_UP		4
#define	GESTURE_DOWN	5
#define	GESTURE_COUNT	6

int bit(int idx){return 1<<idx;}

void computeGesture(float* gestures,int	gestMin,int gestMax,float	delt,float thresh){
	if(delt<=-thresh ){
		float lmag=((-1.0f*delt)-thresh)/(1.0f-thresh);
		gestures[gestMin]=lmag;
	}else gestures[gestMin]=0.0f;
	if(delt>=thresh ){
		float lmag=(delt-thresh)/(1.0f-thresh);
		gestures[gestMax]=lmag;
	}else gestures[gestMax]=0.0f;
}

void App::ONIcontrols(){
	// Compute directional vectors from euler angles
	float cosX = cosf(wx), sinX = sinf(wx), cosY = cosf(wy), sinY = sinf(wy);
	vec3 dx(cosY, 0, sinY);
	vec3 dy(-sinX * sinY,  cosX, sinX * cosY);
	vec3 dz(-cosX * sinY, -sinX, cosX * cosY);

	vec3 dir(0, 0, 0);
	
	vec3	rElbow,rHand,lElbow,lHand;

	float sscale=32;
	for(int p=1;p<4;p++){
		vec3 ldelta(0,0,0);
		vec3 rdelta(0,0,0);
		if(ONIgetJoint(p,XN_SKEL_RIGHT_ELBOW,&rElbow.x)&&ONIgetJoint(p,XN_SKEL_RIGHT_HAND,&rHand.x)){
			rdelta=normalize(rHand-rElbow);
		}
		if(ONIgetJoint(p,XN_SKEL_LEFT_ELBOW,&lElbow.x)&&ONIgetJoint(p,XN_SKEL_LEFT_HAND,&lHand.x)){
			ldelta=normalize(lHand-lElbow);
		}
		float lgestures[GESTURE_COUNT];
		float rgestures[GESTURE_COUNT];

		float fwdGestureThresh=0.75f;
		float lateralGestureThresh=0.75f;
		
		computeGesture(lgestures,GESTURE_FWD,GESTURE_BACK,ldelta.z,fwdGestureThresh);
		computeGesture(lgestures,GESTURE_LEFT,GESTURE_RIGHT,ldelta.x,lateralGestureThresh);
		computeGesture(lgestures,GESTURE_UP,GESTURE_DOWN,ldelta.y,lateralGestureThresh);
		computeGesture(rgestures,GESTURE_FWD,GESTURE_BACK,rdelta.z,fwdGestureThresh);
		computeGesture(rgestures,GESTURE_LEFT,GESTURE_RIGHT,rdelta.x,lateralGestureThresh);
		computeGesture(rgestures,GESTURE_UP,GESTURE_DOWN,rdelta.y,lateralGestureThresh);

		if(lgestures[GESTURE_DOWN]==0.0f)
		{//Left Arm is not down
//			wx+=rdelta.y*0.1f;//Control rotation with right arms
//			wy+=rdelta.x*-0.1f;
		}
		
		if(lgestures[GESTURE_FWD]>0.0f)//Left Arm is FWD
			dir += dz*lgestures[GESTURE_FWD];
		if(lgestures[GESTURE_UP]>0.0f)//Left Arm is UP
			dir -= dz*lgestures[GESTURE_UP];
	}

#if 0
	//mWx, mWy
	float rdx=0.1f;
	float rdy=0.1f;
	if (gestxor&GESTURE_LEFT) wx -= rdx;
	if (gestxor&GESTURE_RIGHT) wx += rdx;
	if (gestxor&GESTURE_UP) wy -= rdy;
	if (gestxor&GESTURE_DOWN) wy += rdy;
#endif

	float l = dot(dir, dir);
	if (l > 0.01f) moveCamera(dir * (1.0f / sqrtf(l)));
}


void	App::debugTransform(vec3&	position,mat3&	rotation,float rad,vec4&	color){
	debugLine(position,position+(rotation.getRow(0)*rad),color4::red);
	debugLine(position,position+(rotation.getRow(1)*rad),color4::green);
	debugLine(position,position+(rotation.getRow(2)*rad),color4::blue);
}

void	App::debugMarker(vec3&	a,float rad,vec4&	color){
	debugLine(a+vec3(-rad,0,0),a+vec3( rad,0, 0),color);
	debugLine(a+vec3(0,-rad,0),a+vec3(0, rad,0),color);
	debugLine(a+vec3(0, 0,-rad),a+vec3(0,0,rad),color);
}


#include "BMFontMessages.h"

void	renderString(const char*	str,vec3&	o=vec3(0,0,0),vec3&	u=vec3(1,0,0),vec3&	v=vec3(0,0,1)){
	renderStringParams	params={(char*)str,&o,&u,&v};
	Listener::sendMessage("fontRenderString",&params);
}


void App::drawFrame(){
	
	//Update simulation
	const float simStep=1.0f/60.0f;//Run the sim at 60 fps..
	const float maxStep=(1.0f/60.0f)*3.0f;//Clamp physics update to 20 fps
	if(time-lastTime > maxStep)
		lastTime=time-maxStep;
	while(lastTime<time){
		Listener::sendMessage("networkUpdate");

		simTime+=simStep;
		lastTime+=simStep;
		Listener::sendMessage("engineStep");
	}


	//Update camera
	float off=0.0f;
	bool	animate=true;
	if(animate && cameraMode==CAM_ORBIT){
		off=sin(time*0.4f);
	}
	vec3 cp(-camPos);
	camQuat.getMatrix(camModelView,vec3(0,0,0));
	mat4 camTargMtx;
	if(camTarget==null)
		camTargMtx=identity4();
	else
		camTargMtx=*camTarget;
	if(cameraMode==CAM_ORBIT){
		cp=(camZoom*camModelView.getForwardVec());
		cp-=camTargMtx.getTranslationVec();
	}else if(cameraMode==CAM_TRACK_EXTERNAL){
		camModelView=inverse(*camTarget);
		camModelView.setTranslationVec(0,0,0);
		cp=(camTargMtx.getTranslationVec()+(-camZoom*camModelView.getForwardVec()))*-1.0f;
	}else if(cameraMode==CAM_TRACK_FPS){
		camYaw-=yawImpulse;
		camPitch-=pitchImpulse;
		pitchImpulse=yawImpulse=0.0f;
		if(camPitch<PI*-0.5f)camPitch=PI*-0.5f;
		if(camPitch>PI* 0.5f)camPitch=PI* 0.5f;
		//mat4 ctarg=camTargMtx;
		mat4 ctarg=rotateZ4(camYaw+PI)*rotateX4((PI*0.5f)-camPitch);
//		ctarg=ctarg * rotateXY4(PI*0.5,PI);
		camModelView=inverse(ctarg);
		camModelView.setTranslationVec(0,0,0);
		
		cp=(camTargMtx.getTranslationVec()+vec3(0,0,2.0f))*-1.0f;//+(ctarg.getForwardVec()*-10.0f);//(-camZoom*camModelView.getForwardVec())+(camZoom*camModelView.getUpVec()*0.2f))*-1.0f;
		//cp+=vec3(0,0,-10.0f);
		//camModelView=lookAt(camTarget->getTranslationVec()-(camZoom*camTarget->getUpVec()),camTarget->getTranslationVec());
	}else if(cameraMode==CAM_TRACK_RTS){
		camYaw-=yawImpulse;
		camPitch-=pitchImpulse;
		pitchImpulse=yawImpulse=0.0f;
		if(camPitch<PI*-0.15f)camPitch=PI*-0.15f;
		if(camPitch>PI* 0.15f)camPitch=PI* 0.15f;
		
		mat4 ctarg=rotateZ4((PI*0.5f)+camYaw)*rotateX4((PI*0.75f)+camPitch);
		//ctarg=ctarg * rotateXY4(PI*0.5,PI);
		camModelView=inverse(ctarg);
		camModelView.setTranslationVec(0,0,0);
		cp=(camTargMtx.getTranslationVec()+(-camZoom*camModelView.getForwardVec()))*-1.0f;

	}else if(cameraMode==CAM_FREE){
	}
	//camQuat = camModelView;
	camModelView.translate(cp);

	camProjection = perspectiveMatrixX(1.5f, width, height, camNear, camFar, false);

	mat4 mProj;
	vec3 spherePos, mCamPos;
	float mWx = 0;
	float mWy = 0;

	static Light lights[] = {
		vec3(-300, 200,  100), 0.00001f,
		vec3( 200, 150, -200), 0.00001f,
	};

	/*
		Various render to texture techniques using FBOs
	*/
	if (menu->isItemChecked(renderCube)){
		/*******************************\
		*                               *
		*       Render to cubemap       *
		*                               *
		\*******************************/

		spherePos = vec3(200 * cosf(time), 90 * sinf(2.43f * time) + 170, 200 * sinf(time));

		glMatrixMode(GL_PROJECTION);
		glLoadTransposeMatrixf(cubemapProjectionMatrix(1, 2000, false));

		// Begin rendering to FBO
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboCube);
		glViewport(0, 0, 512, 512);
		glFrontFace(GL_CCW);

		float wx[] = {  0,    0,    -PI/2, PI/2, 0, 0  };
		float wy[] = { -PI/2, PI/2,  0,    0,    0, PI };
		for (int i = 0; i < 6; i++){
			// Change cubemap face to render to
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, colorCube, 0);

			renderer->clearBuffers(COLOR_BUFFER | DEPTH_BUFFER, vec4(0.1f));

			//drawScene(spherePos, wx[i], wy[i], lights, elementsOf(lights));
		}

		// Return to main framebuffer
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		glViewport(0, 0, width, height);
		glFrontFace(GL_CW);

		// Generate mipmap
		renderer->changeTexture(TMU0, rtColorCube);
		glGenerateMipmapEXT(GL_TEXTURE_CUBE_MAP);

	} else {
		mCamPos = vec3(-350 * cosf(time), 250, -350 * sinf(time));
		mWx = 0.5f;
		mWy = time - PI / 2;

		if (menu->isItemChecked(render2D)){
			/**********************************\
			*                                  *
			*       Render to 2D texture       *
			*                                  *
			\**********************************/

			glMatrixMode(GL_PROJECTION);
			glLoadTransposeMatrixf(camProjection);
#if HEXMAP
			static bool	oneTime=true;
			if(oneTime)
			{
				//oneTime=false;
				// Begin rendering to FBO
				glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo2D);
				glViewport(0, 0, mapTextureDim, mapTextureDim);

				//drawScene(mCamPos, mWx, mWy, lights, elementsOf(lights));
				Listener::sendMessage("renderHexMap",renderer);
				static bool first=true;
				if(first){
					first=false;
					
					Image img;

					img.create(FORMAT_RGB8, mapTextureDim,mapTextureDim, 1, 1);
					glReadPixels(0, 0, mapTextureDim,mapTextureDim, GL_RGB, GL_UNSIGNED_BYTE, img.getPixels());
					img.flip();
					char fileName[32];
					bool success=false;
					
					DeleteFile("hexmap.dds");
					for (int i = 0; i < 100; i++){
						sprintf(fileName, "hexmap.dds", i);
						if (img.saveDDS(fileName, false)) success= true;
					}
				}
				// Return to main framebuffer
				glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
				renderer->changeTexture(TMU0, rtColor2D);
				renderer->apply();
				renderer->generateMipmap(rtColor2D);
				glViewport(0, 0, width, height);
			}
#endif
		} else {
			/**********************************\
			*                                  *
			*       Render to shadow map       *
			*                                  *
			\**********************************/
#if 0
			mProj = perspectiveMatrixX(0.9f, 1, 1, 10, 1000, false);
			glMatrixMode(GL_PROJECTION);
			glLoadTransposeMatrixf(mProj);

			// Begin rendering to FBO
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboDepth);
			glViewport(0, 0, mapTextureDim, mapTextureDim);

				renderer->changeMask(ALL);
				glClear(GL_DEPTH_BUFFER_BIT);
				drawSceneDepth(mCamPos, mWx, mWy);

			// Return to main framebuffer
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
			glViewport(0, 0, width, height);
#endif
		}

	}
	
	for(int i=0;i<array_size(waveGenerators);i++){
		waveGenerators[i]=(float)sin((time*0.25f)*(i+1));
	}
#if 0
	renderer->clearBuffers(COLOR_BUFFER | DEPTH_BUFFER);
#else
	renderer->clearBuffers(DEPTH_BUFFER | (blurFactor==0.0f?COLOR_BUFFER:0), vec4(0.0f));
	/*	mat4 modelview = identity4();
		
		modelview = modelview * rotateXY4(PI*waveGenerators[1],PI*waveGenerators[2]);//PI*0.5);
		//vec4 v=vec4(0,0.0,10.0,0.0);
		modelview.translate(vec3(0,0.0,-10.0*waveGenerators[0]));
		//modelview.translate(-camPos);

		glMatrixMode(GL_PROJECTION);
		glLoadTransposeMatrixf(orthoMatrix(0, 1, 0, 1, -1, 1, false));

		glMatrixMode(GL_MODELVIEW);
		glLoadTransposeMatrixf(modelview);
		*/
	if(blurFactor!=0.0f){
		renderer->changeProjectionMatrix(orthoMatrix(0, 1, 0, 1, -1, 1, false));
		renderer->changeModelViewMatrix(identity4());
		glColor4f(0,0,0,1.0f-blurFactor);
		renderer->setBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		drawFSQuad();
	}
#endif

	if (menu->isItemChecked(renderCube)){
#if 0
		// Draw a sphere with the newly rendered cubemap for reflection and refraction
		mat4 modelview = rotateXY4(-wx, -wy);
		modelview.translate(-camPos);

		glMatrixMode(GL_PROJECTION);
		glLoadTransposeMatrixf(camProjection);

		glMatrixMode(GL_MODELVIEW);
		glLoadTransposeMatrixf(modelview);

		renderer->setCullFace(BACK);
		renderer->setShader(sphereEnv);
		renderer->setTexture("Env", rtColorCube);
		renderer->setShaderConstant3f("camPos", camPos);
		renderer->setShaderConstant3f("spherePos", spherePos);
		renderer->apply();

		sphere->draw(renderer);
#endif	
	} else {
		// Output render target / shadow map in the upper right corner
#if 0
		renderer->changeProjectionMatrix(orthoMatrix(0, 1, 0, 1, -1, 1, false));
		renderer->changeModelViewMatrix(identity4());
		
		if (menu->isItemChecked(render2D)){
			renderer->setTexture(TMU0, rtColor2D);
		} else {
			renderer->setShader(viewDepth);
			renderer->setTexture(TMU0, rtDepth2D);
		}
		renderer->apply();
		glColor3f(1, 1, 1);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 1);
			glVertex2f(0.7f, 0.0f);
			glTexCoord2f(1, 1);
			glVertex2f(1.0f, 0.0f);
			glTexCoord2f(1, 0);
			glVertex2f(1.0f, 0.3f * width / height);
			glTexCoord2f(0, 0);
			glVertex2f(0.7f, 0.3f * width / height);
		glEnd();
#endif
	}

	glMatrixMode(GL_PROJECTION);
	glLoadTransposeMatrixf(camProjection);
	


	// Draw the main scene
	if (menu->isItemChecked(renderDepth)){
		mat4 shadowMvp = mProj * rotateXY4(-mWx, -mWy);
		shadowMvp.translate(-mCamPos);

		shadowMvp = translate(0.5f, 0.5f, 0.5f) * scale4(0.5f, 0.5f, 0.5f) * shadowMvp;

		Light light = { mCamPos, 0.000002f };

		drawScene(camPos, wx, wy, &light, 1, &shadowMvp);
	} else {
		drawScene(camPos, wx, wy, lights, elementsOf(lights));
	}


	
	Listener::sendMessage("engineDrawSelected");


	glMatrixMode(GL_PROJECTION);
	glLoadTransposeMatrixf(camProjection);

	glMatrixMode(GL_MODELVIEW);
	glLoadTransposeMatrixf(camModelView);


	


	Listener::sendMessage("engineDoPicking");
	
	//Draw debug lines
	//glBlendFunc(GL_ONE,GL_ONE_MINUS_DST_COLOR);
	//static	ShaderID	txonlyShader=-1;
	//if(txonlyShader==-1)
	//	txonlyShader=getShader("txonly.shd","");
	//renderer->setShader(0);//txonlyShader);
	//renderer->apply();
	if(debugLines.size()){
		glEnable(GL_DEPTH_TEST);
		glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA);
		//glDisable(GL_BLEND);
		for(int t=0;t<2;t++){
			if(t==0)
				glDepthFunc(t?GL_GREATER:GL_LESS);

			glBegin(GL_LINES);
			int top=debugLines.size();
			int ctop=debugColors.size();
			while(top>1){
				glColor4fv(debugColors[--ctop]);
				glVertex3fv(debugLines[--top]);
				glVertex3fv(debugLines[--top]);
			}
			glEnd();
		}
		debugColors.clear();
		debugLines.clear();
	}

	//renderer->setShader(0);
	float aspect=(float)this->width/(float)this->height;//identity4());//
	renderer->changeProjectionMatrix(orthoMatrix(-aspect, aspect, 1,-1, -1, 1, false));
	renderer->changeModelViewMatrix(identity4());
	
#if 1
	renderer->setTexture(TMU0,titleTexture);
	renderer->setBlending(SRC_ALPHA, ONE_MINUS_SRC_ALPHA);
	renderer->setMask(COLOR);
	float aval=(abs(waveGenerators[4])*0.5f)+0.5f;
	glColor4f(aval,aval,aval,aval);
	renderer->apply();

	
	glPushMatrix();
	glTranslatef(0.8f,1.1f+((1.0f-abs(waveGenerators[1]))*0.25f),0);
	glScalef(0.5f,0.5f,0.0f);
	drawXYQuad(1.0f);
	glPopMatrix();
	
	if (optionsMenu->isItemChecked(optionsShowFPS)){
		Listener::sendMessage("fontStartRendering");
		glBegin(GL_QUADS);
	//	renderString("TEST",vec3(0.0f,0.75f,0.0f),vec3(0.01f,0,0),vec3(0,0.01f,0));
			Listener::sendMessage("renderLog");
		glEnd();
	}
	//renderer->setTexture(TMU0,titleTexture);

#endif
#if 0

	ONIupdate();

	//glBegin(GL_LINES);
	glEnable(GL_BLEND);
	glMatrixMode(GL_MODELVIEW);
	vec3	ax(1,0,0);
	vec3	ay(0,1,0);
	renderer->setTexture(TMU0, lightFlareTexture);
	renderer->setBlending(GL_ONE, GL_ONE);
	renderer->setMask(COLOR);
	renderer->apply();

    glColor3f(1, 1, 1);
	ONIcontrols();
	
	//glEnd();
#endif
}


bool	networkIsClient=false;
bool	networkIsServer=false;
bool	networkIsBroken=false;

class AppListener : public Listener
{
public:
	virtual bool	message(const char *msg=null,void*	params=null){
		if(msg=="networkDataFromServer"){
			logstr(format("Got data from server:%s",params));
		}else
		if(msg=="networkDataFromClient"){
			logstr(format("Got data from client:%s",params));
		}else
		if(msg=="networkConnectedAsClient"){
			networkIsClient=true;
			logstr("Client connected succesfully.");
		}else 
		if(msg=="networkConnectingToServer"){
			logstr("Client attempting to connect to server...");
		}else 
		if(msg=="networkServerStarted"){
			networkIsServer=true;
			logstr("Server started. Starting client...");
			Listener::sendMessage("networkStartClient");
		}else 
		if(msg=="networkFailToConnect" || msg=="networkClientIOFailure"){
			if(networkIsServer==false){
				logstr("Client connect failed. Starting server...");
				Listener::sendMessage("networkStartServer");
			}else{
				logstr("Server started but connect failed. No loopback? Network is broken.");
				networkIsBroken=true;
			}
		}else
		if(msg=="networkFailToStartServer"){
			logstr("Server start failed.. Network is broken.");
			networkIsBroken=true;
		}
		return true;
	}
	AppListener(){
		Listener::listen(this,"networkConnectedAsClient");
		Listener::listen(this,"networkServerStarted");
		Listener::listen(this,"networkDataFromClient");
		Listener::listen(this,"networkDataFromServer");
		Listener::listen(this,"networkClientDropped");
		Listener::listen(this,"networkFailToConnect");
		Listener::listen(this,"networkFailToStartServer");
		Listener::listen(this,"networkIOFailure");
		Listener::listen(this,"networkClientIOFailure");
		Listener::listen(this,"networkClientStopped");
		Listener::listen(this,"networkServerStopped");
	}
};

AppListener gAppListener;
